package com.bingxue.edu.management.education.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.bingxue.edu.common.enums.JoinTypeEnum;
import com.bingxue.edu.common.enums.StudentClassEnum;
import com.bingxue.edu.common.util.EduUtil;
import com.bingxue.edu.framework.common.model.PageQuery;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.exception.BusinessException;
import com.bingxue.edu.management.education.model.entity.ClassInfo;
import com.bingxue.edu.management.education.model.entity.EnrollChangeRecord;
import com.bingxue.edu.management.education.model.entity.Enrollment;
import com.bingxue.edu.management.education.model.entity.Lesson;
import com.bingxue.edu.management.education.model.entity.LessonStudent;
import com.bingxue.edu.management.education.model.query.ClassChangeQuery;
import com.bingxue.edu.management.education.model.req.ClassChangeReq;
import com.bingxue.edu.management.education.model.req.ClassChangeRevertReq;
import com.bingxue.edu.management.education.model.resp.ClassChangeDetailResp;
import com.bingxue.edu.management.education.model.resp.ClassChangeResp;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.bingxue.edu.common.enums.ChangeTypeEnum.CHANGE_CLASS;
import static com.bingxue.edu.common.enums.ChangeTypeEnum.CHANGE_LESSON;
import static com.bingxue.edu.common.enums.OperateSourceEnum.PARENT;
import static com.bingxue.edu.common.enums.OperateStatusEnum.REVERTED;
import static com.bingxue.edu.common.enums.OperateStatusEnum.SUCCESS;
import static com.bingxue.edu.management.education.model.entity.table.ClassInfoTableDef.CLASS_INFO;
import static com.bingxue.edu.management.education.model.entity.table.EnrollChangeRecordTableDef.ENROLL_CHANGE_RECORD;
import static com.bingxue.edu.management.education.model.entity.table.StudentTableDef.STUDENT;

/**
 * 转班调课实现类
 *
 * @author zq
 * @since 2024/08/29
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ClassChangeService {

    private final EnrollmentService enrollmentService;
    private final EnrollPermissionService enrollPermissionService;
    private final ClassInfoService classInfoService;
    private final LessonService lessonService;
    private final LessonStudentService lessonStudentService;
    private final EnrollChangeRecordService enrollChangeRecordService;

    /**
     * 转班调课
     * 转班调课只要符合条件，直接通过，不需要审核。
     */
    @Transactional(rollbackFor = Exception.class)
    public void createClassChangeRecord(ClassChangeReq req) {
        validateChangeReq(req);
        // 查询原班级、新班级信息、报名信息
        ClassInfo oldClass = classInfoService.getById(req.getOldClassId());
        ClassInfo newClass = classInfoService.getById(req.getNewClassId());
        ValidationUtils.throwIfNull(oldClass, "转出班级不存在");
        ValidationUtils.throwIfNull(newClass, "转入班级不存在");
        Enrollment enrollment = enrollmentService.getStudentEnrollment(req.getOldClassId(), req.getStudentId());
        ValidationUtils.throwIfNull(enrollment, "未找到报名记录");
        // 校验班级信息
        validateClassInfo(oldClass, newClass);
        // 家长操作需判断时间和权限
        if (req.getOperateSource() == PARENT) {
            EduUtil.checkClassStatus(oldClass);
            EduUtil.checkClassStatus(newClass);
            EduUtil.checkChangeTime(oldClass, newClass);
            enrollPermissionService.checkPermission(req.getStudentId(), newClass);
        }
        if (req.getChangeType() == CHANGE_CLASS) {
            changeClass(req, enrollment, oldClass, newClass);
        } else if (req.getChangeType() == CHANGE_LESSON) {
            changeLesson(req, enrollment, oldClass, newClass);
        }
    }

    /**
     * 撤销转班调课
     * 目前只允许调课
     */
    @Transactional(rollbackFor = Exception.class)
    public void revertClassChange(ClassChangeRevertReq req) {
        // 查询报名记录信息
        EnrollChangeRecord record = enrollChangeRecordService.getById(req.getRecordId());
        ValidationUtils.throwIfNull(record,"调课记录不存在");
        ValidationUtils.throwIfNotEqual(CHANGE_LESSON, record.getChangeType(),"仅允许撤销调课记录");
        ValidationUtils.throwIfNotEqual(SUCCESS, record.getOperateStatus(),"调课记录未生效");
        // 查询报名信息
        Enrollment enrollment = enrollmentService.getById(record.getEnrollmentId());
        ValidationUtils.throwIfNull(enrollment,"报名记录不存在");
        // 调整剩余调课次数+1
        enrollmentService.increaseLessonChangeCount(enrollment.getId(),1);
        Integer lessonIndex = record.getOriginalLessonIndexes().get(0);
        // 查询原班级课次信息
        LessonStudent oldLessonStudent = lessonStudentService.getOneByLessonIndex(enrollment.getId(), lessonIndex, StudentClassEnum.CHANGE_LESSON);
        ValidationUtils.throwIfNull(oldLessonStudent,"原课次信息不存在");
        // 查询新班级课次信息
        LessonStudent newLessonStudent = lessonStudentService.getOneByLessonIndex(enrollment.getId(), lessonIndex, StudentClassEnum.NORMAL);
        ValidationUtils.throwIfNull(newLessonStudent,"转入课次信息不存在");
        if (req.getOperateSource() == PARENT) {
            ValidationUtils.throwIf(newLessonStudent.getSignInStatus(),"转入课次已完课");
        }
        // 删除学生新课次信息
        lessonStudentService.removeById(newLessonStudent);
        // 恢复学生旧课次信息
        oldLessonStudent.setStudentClassId(enrollment.getClassId());
        oldLessonStudent.setStudentClassStatus(StudentClassEnum.NORMAL);
        lessonStudentService.updateById(oldLessonStudent);
        // 报名变更记录信息状态改为已撤销
        record.setOperateStatus(REVERTED);
        enrollChangeRecordService.updateById(record);
        log.info("撤销调课成功, req: {}", req);
    }

    /**
     * 转班
     */
    private void changeClass(ClassChangeReq req, Enrollment enrollment, ClassInfo oldClass, ClassInfo newClass) {
        if (req.getOperateSource() == PARENT) {
            // 检查学生剩余转班次数
            EduUtil.checkClassChangeRemainCount(enrollment);
            // 检查新班级名额
            EduUtil.checkAvailableCount(newClass);
        }
        // 学生移除原班级
        classInfoService.removeStudentFromClass(req.getStudentId(), req.getOldClassId(), StudentClassEnum.CHANGE_CLASS);
        // 学生加入新班级
        classInfoService.addStudentToClass(req.getStudentId(), req.getNewClassId(), enrollment.getId(), JoinTypeEnum.CHANGE_CLASS);
        // 学生移除原课次
        List<LessonStudent> oldLessonStudentList = getChangeLessonList(req, enrollment);
        lessonStudentService.updateStudentClassStatus(oldLessonStudentList, StudentClassEnum.CHANGE_CLASS);
        // 学生创建新课次 index -> lesson
        List<Integer> lessonIndexList = oldLessonStudentList.stream().map(LessonStudent::getLessonIndex).sorted().toList();
        Map<Integer, Lesson> newClassLessonMap = lessonService.getLessonByIndex(req.getNewClassId(), lessonIndexList)
                .stream().collect(Collectors.toMap(Lesson::getLessonIndex, it -> it));
        ValidationUtils.throwIfNotEqual(newClassLessonMap.size(), oldLessonStudentList.size(), "班级课次不一致");
        List<LessonStudent> newLessonStudentList = oldLessonStudentList.stream().map(oldLessonStudent -> {
            LessonStudent newLessonStudent = copyLessonStudent(oldLessonStudent, newClassLessonMap.get(oldLessonStudent.getLessonIndex()));
            newLessonStudent.setStudentClassId(newClass.getId());
            newLessonStudent.setStudentJoinType(JoinTypeEnum.CHANGE_CLASS);
            newLessonStudent.setLessonHour(newClass.getLessonHourFactor());
            return newLessonStudent;
        }).toList();
        lessonStudentService.saveBatch(newLessonStudentList);
        // 报名信息修改，保存报名信息变更记录
        EnrollChangeRecord enrollChangeRecord = enrollChangeRecordService.initChangeRecord(req, enrollment, oldClass, newClass, lessonIndexList);
        enrollmentService.updateEnrollment(enrollment, enrollChangeRecord);

        log.info("转班成功, req: {}", req);
    }



    /**
     * 调课
     */
    private void changeLesson(ClassChangeReq req, Enrollment enrollment, ClassInfo oldClass, ClassInfo newClass) {
        if (req.getOperateSource() == PARENT) {
            // 检查学生剩余调课次数
            EduUtil.checkLessonChangeRemainCount(enrollment);
        }
        // 学生移除原课次
        List<LessonStudent> oldLessonStudentList = getChangeLessonList(req, enrollment);
        // 不能对同一课次进行多次调课，需要撤销后再次申请
        ValidationUtils.throwIf(oldLessonStudentList.size() != 1, "调课数量不正确");
        LessonStudent oldLessonStudent = oldLessonStudentList.get(0);
        ValidationUtils.throwIfNotEqual(oldLessonStudent.getStudentClassId(), oldLessonStudent.getLessonClassId(), "不允许对同一课次多次调课，请先撤销上次调课操作");
        // 设置原课次已调出
        lessonStudentService.updateStudentClassStatus(oldLessonStudentList, StudentClassEnum.CHANGE_LESSON);
        // 查询新课次
        Lesson newLesson = lessonService.getLessonByIndex(req.getNewClassId(), oldLessonStudent.getLessonIndex());
        ValidationUtils.throwIfNull(newLesson, "转入班级对应课次不存在");
        // 创建新学生课次
        LessonStudent newLessonStudent = copyLessonStudent(oldLessonStudent, newLesson);
        newLessonStudent.setStudentClassId(enrollment.getClassId());
        newLessonStudent.setStudentJoinType(JoinTypeEnum.CHANGE_LESSON);
        newLessonStudent.setLessonHour(newClass.getLessonHourFactor());
        lessonStudentService.save(newLessonStudent);
        // 报名信息修改，保存报名信息变更记录
        EnrollChangeRecord enrollChangeRecord = enrollChangeRecordService.initChangeRecord(req, enrollment, oldClass, newClass, List.of(oldLessonStudent.getLessonIndex()));
        enrollmentService.updateEnrollment(enrollment, enrollChangeRecord);

        log.info("调课成功, req: {}", req);
    }


    private void validateClassInfo(ClassInfo oldClass, ClassInfo newClass) {
        // 校验班级年份是否一致
        ValidationUtils.throwIf(!oldClass.getClassYear().equals(newClass.getClassYear()), "原班级和转入班级的年份不一致");
        // 校验班级学期是否一致
        ValidationUtils.throwIf(!oldClass.getTerm().equals(newClass.getTerm()), "原班级和转入班级的学季不一致");
        // 校验班级年级是否一致
        ValidationUtils.throwIf(!oldClass.getGrade().equals(newClass.getGrade()), "原班级和转入班级的年级不一致");
        // 校验科目是否一致
        ValidationUtils.throwIf(!oldClass.getSubject().equals(newClass.getSubject()), "原班级和转入班级的科目不一致");
        // 校验班级类别是否一致
        ValidationUtils.throwIf(!oldClass.getClassCategory().equals(newClass.getClassCategory()), "原班级和转入班级的班级类型不一致");
        // 校验单价是否一致
        ValidationUtils.throwIf(!oldClass.getUnitPrice().equals(newClass.getUnitPrice()), "原班级和转入班级的单价不一致");
        // 课次数量是否一致
        ValidationUtils.throwIf(!oldClass.getTotalLessonCount().equals(newClass.getTotalLessonCount()), "原班级和转入班级的排课数量不一致");
    }

    /**
     * 获取转班调课课次
     * 如果转班，且设置了只转剩余课次，则通过查询获取。否则根据自定义传入课次顺序获取
     */
    private List<LessonStudent> getChangeLessonList(ClassChangeReq req, Enrollment enrollment) {
        if (req.isOnlyRemainLesson() && req.getChangeType() == CHANGE_CLASS) {
            return lessonStudentService.getListByEnrollmentId(enrollment.getId(), true);
        } else {
            return lessonStudentService.getListByLessonIndex(enrollment.getId(), req.getChangeLessonIndexList());
        }
    }

    /**
     * 验证转班调课请求
     */
    private void validateChangeReq(ClassChangeReq req) {
        ValidationUtils.throwIf(req.getOldClassId().equals(req.getNewClassId()), "转入班级不能为原班级");
        if (req.getChangeType() == CHANGE_CLASS) {
            // 家长端仅允许转入剩余未上课次
            if (req.getOperateSource() == PARENT && !req.isOnlyRemainLesson()) {
                throw new BusinessException("仅允许转入剩余未上课次");
            }
            // 自定义课次列表，不能为空
            if (!req.isOnlyRemainLesson() && CollUtil.isEmpty(req.getChangeLessonIndexList())) {
                throw new BusinessException("转入课次不能为空");
            }
        } else if (req.getChangeType() == CHANGE_LESSON) {
            // 调课仅限1节
            ValidationUtils.throwIf(CollUtil.size(req.getChangeLessonIndexList()) != 1, "仅限调1节课");
        } else {
            throw new BusinessException("操作类型不允许");
        }
    }

    public Page<ClassChangeResp> getClassChangePage(ClassChangeQuery query, PageQuery pageQuery) {
        QueryWrapper queryWrapper = buildChangeQueryWrapper()
                .where(ENROLL_CHANGE_RECORD.STUDENT_ID.eq(query.getStudentId()))
                    .and(ENROLL_CHANGE_RECORD.ORIGINAL_CLASS_ID.eq(query.getOriginalClassId()))
                    .and(ENROLL_CHANGE_RECORD.TARGET_CLASS_ID.eq(query.getTargetClassId()))
                    .and(ENROLL_CHANGE_RECORD.CHANGE_TYPE.eq(query.getChangeType()))
                    .and(ENROLL_CHANGE_RECORD.OPERATE_SOURCE.eq(query.getOperateSource()))
                    .and(ENROLL_CHANGE_RECORD.OPERATE_STATUS.in(query.getOperateStatus()))
                    .and(ENROLL_CHANGE_RECORD.CREATE_TIME.ge(query.getCreateDateFrom()))
                    .and(ENROLL_CHANGE_RECORD.CREATE_TIME.lt(query.getCreateDateTo()))
                    .and(CLASS_INFO.as("cls1").GRADE.eq(query.getGrade()))
                .orderBy(ENROLL_CHANGE_RECORD.ID.desc());
        return enrollChangeRecordService.getMapper().paginateWithRelationsAs(pageQuery.toPage(), queryWrapper, ClassChangeResp.class);
    }

    private QueryWrapper buildChangeQueryWrapper() {
        return QueryWrapper.create()
                .select(
                        // TODO DEFAULT_COLUMNS方式，create_time不能正常赋值，未知原因
//                        ArrayUtil.addAll(ENROLL_CHANGE_RECORD.DEFAULT_COLUMNS, STUDENT.DEFAULT_COLUMNS),
                        ENROLL_CHANGE_RECORD.DEFAULT_COLUMNS,
                        STUDENT.ID,
                        STUDENT.NAME,
                        STUDENT.AVATAR,
                        STUDENT.GENDER,
                        STUDENT.PARENT_ID,
                        STUDENT.PARENT_PHONE,
                        CLASS_INFO.as("cls1").NAME.as(ClassChangeResp::getOriginalClassName),
                        CLASS_INFO.as("cls1").LEAD_TEACHER_ID.as(ClassChangeResp::getOriginalLeadTeacherId),
                        CLASS_INFO.as("cls2").NAME.as(ClassChangeResp::getTargetClassName),
                        CLASS_INFO.as("cls2").LEAD_TEACHER_ID.as(ClassChangeResp::getTargetLeadTeacherId)
                )
                .from(ENROLL_CHANGE_RECORD)
                    .leftJoin(STUDENT).on(STUDENT.ID.eq(ENROLL_CHANGE_RECORD.STUDENT_ID))
                    .leftJoin(CLASS_INFO).as("cls1").on(CLASS_INFO.as("cls1").ID.eq(ENROLL_CHANGE_RECORD.ORIGINAL_CLASS_ID))
                    .leftJoin(CLASS_INFO).as("cls2").on(CLASS_INFO.as("cls2").ID.eq(ENROLL_CHANGE_RECORD.TARGET_CLASS_ID));
    }

    public ClassChangeDetailResp getClassChangeDetail(String id) {
        ValidationUtils.throwIfNull(id,"id不能空");
        QueryWrapper queryWrapper = buildChangeQueryWrapper().where(ENROLL_CHANGE_RECORD.ID.eq(id));
        ClassChangeDetailResp detail = enrollChangeRecordService.getMapper().selectOneWithRelationsByQueryAs(queryWrapper, ClassChangeDetailResp.class);

        //根据课次顺序查询课次信息
        List<Integer> originalLessonIndexList = Convert.toList(Integer.class, detail.getOriginalLessonIndexes());
        List<Lesson> originalLesson = lessonService.getLessonByIndex(detail.getOriginalClassId(), originalLessonIndexList);
        detail.setOriginalLesson(originalLesson);
        List<Integer> targetLessonIndexList = Convert.toList(Integer.class, detail.getTargetLessonIndexes());
        List<Lesson> targetLesson = lessonService.getLessonByIndex(detail.getTargetClassId(), targetLessonIndexList);
        detail.setTargetLesson(targetLesson);

        return detail;
    }

    /**
     * 根据新课次信息，从旧课次学生信息中复制创建新的课次学生信息
     * @param oldLessonStudent 旧课次学生信息
     * @param newLesson 新课次信息
     * @return 新课次学生信息
     */
    private LessonStudent copyLessonStudent(LessonStudent oldLessonStudent, Lesson newLesson) {
        LessonStudent newLessonStudent = new LessonStudent();
        BeanUtil.copyProperties(oldLessonStudent, newLessonStudent);
        newLessonStudent.setId(null);
        newLessonStudent.setLessonId(newLesson.getId());
        newLessonStudent.setLessonClassId(newLesson.getClassId());
        newLessonStudent.setStudentClassId(newLesson.getClassId());
        newLessonStudent.setStudentClassStatus(StudentClassEnum.NORMAL);
        return newLessonStudent;
    }
}
